#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "assembly_types.h"
#include "assembly_returns.h"

#define SKIP_WHITESPACE "%*[ \t\v]"
#define SKIP_TO_EOL "%*[^\n]"

#define BYTECODE_VERSION 1
#define DIRECTIVES_COUNT 2
#define DIRECTIVE_MAX_LENGTH 10
#define OPTIONS_COUNT 1
#define OPTION_MAX_LENGTH 2

#define DELTA_SIZE 16
#define CODE_PORTION_SIZE 16

#define FILE_ERROR_MESSAGE(f)	if(ferror(f))\
								{\
									perror("assembly!/ Error occured");\
									return ERROR_OCCURED_A;\
								}\
								else if(feof(f))\
								{\
									fprintf(stderr, "assembly!/ Unexpected end of file. Line %llu.\n", linecount);\
									return UNEXPECTED_END_OF_FILE_A;\
								}


PBytecode assembly(PBytecode, char *, unsigned long long int *);

#pragma pack(push, 2)
typedef struct FH {//FileHeader
				uint16_t signature;
				uint32_t version;
				uint32_t constsAmmount;
				uint32_t constsSize;
				} Fileheader, PFileheader;

typedef struct IDNAMM {//ID aNd AMMounts
				uint16_t fid;
				uint32_t vAmm;
				uint32_t aAmm;
				} IDAndAmmounts, *PIDAndAmmounts;
#pragma pack(pop)

#pragma pack(push, 4)
typedef struct SIZES {//SIZES
				uint32_t size;
				uint32_t bsize;
				uint32_t ssize;
				} Sizes, *PSizes;
#pragma pack(pop)

int main (int argc, char **argv)
{
	PFunctionsList funcList = NULL;
	PBytecode instate, outstate;
	Fileheader fileheader;
	Sizes szs;
	IDAndAmmounts idnamms;
	FILE *source, *compiled;
	uint16_t mainID;
	uint32_t funcsAmmount;
	size_t i, j, signPoolSize = DELTA_SIZE, signSize, codePoolSize = DELTA_SIZE, codeSize;
	void *tmp;
	long int vAmm, aAmm, fAmm, funcID;
	unsigned long long int linecount = 1, polynom;
	int character;
	char *funcSignature, *funcCode, *compiledName = "btcd.green", *sourceName = NULL;// funcCode also used as constants pool
	char directive[DIRECTIVE_MAX_LENGTH + 1], funcCodePortion[CODE_PORTION_SIZE], tmpchar, isNewLine;
	const char dirlist[DIRECTIVES_COUNT][DIRECTIVE_MAX_LENGTH + 1] = {
																	"function",		//0
																	"efunction"		//1
																	};
	const char optlist[OPTIONS_COUNT][OPTION_MAX_LENGTH + 1] = {
																"-o"	//0
																};
	for(i = 1; i < argc; i++)
	{
		j = 0;
		while( (j < OPTIONS_COUNT) && (strcmp(argv[i],optlist[j]) != 0) ) j++;
		switch(j)
		{
			case 0:if( (i + 1) == argc )
					{
						fprintf(stderr, "assembly!/ Option %s has no argument.\n", argv[i]);
						return OPT_WITH_NO_ARG;
					}
					compiledName = argv[++i];
					break;
			default:sourceName = argv[i];
					break;
		}
	}
	if(sourceName == NULL)
	{
		fprintf(stderr, "assembly!/ No source file.\n");
		return NO_SOURCE_FILE_A;
	}
	source = fopen(sourceName, "r");
	if(source == NULL)
	{
		fprintf(stderr, "assembly!/ No such source file.\n");
		return NO_SUCH_SOURCE_FILE_A;
	}
	fAmm = 0;
	funcSignature = (char*) malloc(signPoolSize*sizeof(char));
	funcCode = (char*) malloc(codePoolSize*sizeof(char));
	instate = (PBytecode) malloc(sizeof(Bytecode));
	instate->strings = NULL;
	instate->bytecode = NULL;
	instate->labels = NULL;
	instate->queue = NULL;
	instate->bytecodeSize = 0;
	////////////////////////////////////////////////////////////////////
	while(!feof(source))
	{
		do
		{
			fscanf(source, SKIP_WHITESPACE);
			character = fgetc(source);
			if(character == EOF)
			{
				break;
			}
			tmpchar = (char) character;
			if(tmpchar == '\n')
			{
				linecount++;
				continue;
			}
			if(tmpchar == ';')
			{
				if(fscanf(source, SKIP_TO_EOL) == EOF)
				{
					break;
				}
				continue;
			}
			if(tmpchar == '#')
			{
				break;
			}
			else
			{
				fprintf(stderr, "assembly!/ Input error. '#' was expected instead of '%c'. Line %llu.\n", directive[0], linecount);
				return INPUT_ERROR_A;
			}
		} while(1);
		if(fscanf(source, "%10s", directive) == EOF)
		{
			if(feof(source))
			{
				break;
			}
			else if(ferror(source))
			{
				perror("assembly!/ Error occured");
				return ERROR_OCCURED_A;
			}
		}
		i = 0;
		while( (i < DIRECTIVES_COUNT) && (strcmp(directive,dirlist[i]) != 0) ) i++;
		switch(i)
		{
			case 0:fAmm++;
					if(fAmm > UINT32_MAX)
					{
						fprintf(stderr, "assembly!/ There are too many functions. Line %llu.\n", linecount);
						return TOO_MANY_FUNCS_A;
					}
					funcSignature[0] = '\0';
					signSize = 0;				
					if(fscanf(source, "%ld", &funcID) != 1)
					{
						FILE_ERROR_MESSAGE(source);
						do
						{
							character = fgetc(source);
							if(character == EOF)
							{
								FILE_ERROR_MESSAGE(source);
							}
							tmpchar = (char) character;
							////////
							if( !IS_FUNC_SIGN_CHAR(tmpchar, signSize) && (tmpchar != ' ') && (tmpchar != '\n') && (tmpchar != '\t') )
							{
								fprintf(stderr, "assembly!/ Wrong character in the function's name ('%c'). Line %llu.\n", tmpchar, linecount);
								return INVALID_FUNC_SIGNATURE_A;
							}
							////////
							if(signSize >= signPoolSize)
							{
								signPoolSize += DELTA_SIZE;
								funcSignature = (char*) realloc(funcSignature, signPoolSize*sizeof(char));
							}
							funcSignature[signSize] = tmpchar;
							signSize++;
							if(tmpchar == '\n')
							{
								fprintf(stderr, "assembly-/ Unexpected end of line. Line %llu.\n", linecount);
								linecount++;
							}
						}while( (tmpchar != ' ') && (tmpchar != '\n') && (tmpchar != '\t') );
						signSize--;
						funcSignature[signSize] = '\0';
						polynom = 0;
						for(j = signSize; j > 0; j--)
						{
							polynom += funcSignature[j - 1]*POLY_FACTOR;
						}
						funcID = ((uint16_t) (polynom%POLY_DEVIDER));
					}
					if(funcID < 0)
					{
						fprintf(stderr, "assembly!/ Invalid function ID. Need equal to or greater than 0, instead of %ld. Line %llu.\n", funcID, linecount);
						return INVALID_FUNC_ID_A;
					}
					if(funcID > MAX_FUNC_ID)
					{
						fprintf(stderr, "assembly!/ Invalid function ID. Need less than %u, instead of %ld. Line %llu.\n", MAX_FUNC_ID + 1, funcID, linecount);
						return INVALID_FUNC_ID_A;
					}
					vAmm = 0;
					aAmm = 0;
					if(fscanf(source, "%ld%ld", &vAmm, &aAmm) != 2)
					{
						FILE_ERROR_MESSAGE(source);
					}
					if(aAmm < 0)
					{
						fprintf(stderr, "assembly!/ Invalid arguments ammount. Need equal to or greater than 0, instead of %ld. Line %llu.\n", aAmm, linecount);
						return INVALID_ARG_AMMOUNT_A;
					}
					if(aAmm > MAX_VAR_AMMOUNT)
					{
						fprintf(stderr, "assembly!/ Invalid arguments ammount. Need less than %u, instead of %ld. Line %llu.\n", MAX_VAR_AMMOUNT + 1, aAmm, linecount);
						return INVALID_ARG_AMMOUNT_A;
					}
					if(vAmm < 0)
					{
						fprintf(stderr, "assembly!/ Invalid variables ammount. Need equal to or greater than 0, instead of %ld. Line %llu.\n", vAmm, linecount);
						return INVALID_VAR_AMMOUNT_A;
					}
					if(vAmm > MAX_VAR_AMMOUNT)
					{
						fprintf(stderr, "assembly!/ Invalid variables ammount. Need less than %u, instead of %ld. Line %llu.\n", MAX_VAR_AMMOUNT + 1, vAmm, linecount);
						return INVALID_VAR_AMMOUNT_A;
					}
					if(aAmm > vAmm)
					{
						fprintf(stderr, "assembly-/ Arguments ammount is more than variables ammount. Line %llu.\n", linecount);
						aAmm = vAmm;
					}
					fscanf(stderr, SKIP_WHITESPACE);
					character = fgetc(source);
					if(character == EOF)
					{
						FILE_ERROR_MESSAGE(source);
					}
					tmpchar = (char) character;
					if(tmpchar == ';')
					{
						fscanf(source, SKIP_TO_EOL);
					}
					else if(tmpchar == '\n')
					{
						linecount++;
					}
					else
					{
						fprintf(stderr, "assembly!/ Input error. Line %llu.\n", linecount);
						return INPUT_ERROR_A;
					}
					codeSize = 0;
					funcCode[0] = '\0';
					isNewLine = 1;
					while(1)
					{
						if((fgets(funcCodePortion, CODE_PORTION_SIZE, source) == NULL) && (!isNewLine))
						{
							FILE_ERROR_MESSAGE(source);
						}
						if(isNewLine)
						{
							sscanf(funcCodePortion, "%10s", directive);
							if(strcmp(directive, "#efunction") == 0)
							{
								break;//!!!!!!!
							}
							isNewLine = 0;
						}
						if(funcCodePortion[strlen(funcCodePortion) - 1] == '\n')
						{
							isNewLine = 1;
						}
						if( (codeSize + strlen(funcCodePortion)) >= codePoolSize)
						{
							codePoolSize += DELTA_SIZE;
							funcCode = (char*) realloc(funcCode, codePoolSize);
						}
						strcpy(funcCode + codeSize, funcCodePortion);
						codeSize += strlen(funcCodePortion);
					}
					//
					outstate = assembly(instate, funcCode, &linecount);
					if(outstate->bytecode == NULL)
					{
						switch(outstate->bytecodeSize)
						{
							case INPUT_ERROR_A:fprintf(stderr, "assembly!/ Input error. Line %llu.\n", linecount);
												return INPUT_ERROR_A;
							case NO_FREE_MEMORY_A:fprintf(stderr, "assembly!/ There is no free memory. Compilation stoped.\n");
												return NO_FREE_MEMORY_A;
							case DUPLICATE_LABEL_A:fprintf(stderr, "assembly!/ Duplicate label. Line %llu.\n", linecount);
												return DUPLICATE_LABEL_A;
							case NO_SUCH_COMMAND_A:fprintf(stderr, "assembly!/ No such command. Line %llu.\n", linecount);
												return NO_SUCH_COMMAND_A;
							case TOO_FAR_LABEL_A:fprintf(stderr, "assembly!/ Label %s is too far. Line %llu.\n", outstate->queue->label, linecount);
												return TOO_FAR_LABEL_A;
							case INVALID_FUNC_SIGNATURE_A:fprintf(stderr, "assembly!/ Invalid function's signature. Line %llu.\n", linecount);
												return INVALID_FUNC_SIGNATURE_A;
							case INVALID_FUNC_ID_A:fprintf(stderr, "assembly!/ Invalid function's ID. Line %llu.\n", linecount);
												return INVALID_FUNC_ID_A;
							default:fprintf(stderr, "assembly!/ Unknown error. Line %llu.\n", linecount);
									return ERROR_OCCURED_A;
						}
					}
					if(outstate->queue != NULL)
					{
						fprintf(stderr, "assembly!/ %s - no such label. Line %llu.\n", outstate->queue->label, outstate->queue->line);
						return NO_SUCH_LABEL_A;
					}
					while(outstate->labels != NULL)
					{
						tmp = (void*) outstate->labels->next;
						free(outstate->labels);
						outstate->labels = (PLabels) tmp;
					}
					instate->strings = outstate->strings;
					//
					if(funcList == NULL)
					{
						mainID = (uint16_t) funcID;
					}
					tmp = malloc(sizeof(FunctionsList));
					((PFunctionsList) tmp)->next = funcList;
					funcList = (PFunctionsList) tmp;
					//
					funcList->signature = (char*) malloc((signSize + 1)*sizeof(char));
					strcpy(funcList->signature, funcSignature);
					funcList->bytecode = outstate->bytecode;
					if(outstate->bytecodeSize > UINT32_MAX)
					{
						fprintf(stderr, "assembly!/ The bytecode of function is too long. Line %llu.\n", linecount);
						return TOO_LONG_BYTECODE_A;
					}
					funcList->bytecodeSize = (uint32_t) outstate->bytecodeSize;
					funcList->varAmmount = (uint32_t) vAmm;
					funcList->argAmmount = (uint32_t) aAmm;
					funcList->id = (uint16_t) funcID;
					free(outstate);
					linecount++;
					break;
			case 1:fprintf(stderr, "assembly!/ End of function without beginning was found. Line %llu.\n", linecount);
					return END_WITHOUT_BEGINNING_A;
			default:fprintf(stderr, "assembly!/ %s - no such directive. Line %llu.\n", directive, linecount);
					return NO_SUCH_DIRECTIVE_A;
		}
	}
	////////////////////////////////////////////////////////////////////
	fclose(source);
	funcsAmmount = (uint32_t) fAmm;
	if(funcsAmmount == 0)
	{
		fprintf(stderr, "assembler!/ There are too few functions. Need more than 0. Line %llu.\n", linecount);
		return TOO_FEW_FUNCS_A;
	}
	fileheader.signature = (uint16_t) 0xBABA;
	fileheader.version = BYTECODE_VERSION;
	codeSize = 0;
	i = 0;
	while(instate->strings != NULL)
	{
		if( codePoolSize <= (codeSize + strlen(instate->strings->string) + 1) )
		{
			codePoolSize += strlen(instate->strings->string) + 1;
			funcCode = (char*) realloc(funcCode, codePoolSize);
		}
		memcpy( (funcCode + codeSize), (instate->strings->string), (strlen(instate->strings->string) + 1) );
		codeSize += strlen(instate->strings->string) + 1;
		i++;
		tmp = (void*) instate->strings;
		instate->strings = instate->strings->next;
		free(tmp);
	}
	fileheader.constsAmmount = i;
	fileheader.constsSize = codeSize;
	compiled = fopen(compiledName, "wb");
	if(compiled == NULL)
	{
		FILE_ERROR_MESSAGE(compiled);
	}
	if(fwrite(&fileheader, sizeof(Fileheader), 1, compiled) != 1)
	{
		FILE_ERROR_MESSAGE(compiled);
	}
	if(fwrite(funcCode, codeSize, 1, compiled) != 1)
	{
		FILE_ERROR_MESSAGE(compiled);
	}
	if(fwrite(&mainID, sizeof(uint16_t), 1, compiled) != 1)
	{
		FILE_ERROR_MESSAGE(compiled);
	}
	if(fwrite(&funcsAmmount, sizeof(uint32_t), 1, compiled) != 1)
	{
		FILE_ERROR_MESSAGE(compiled);
	}
	while(funcList != NULL)
	{
		szs.size = 4*sizeof(uint32_t) + sizeof(uint16_t) + (strlen(funcList->signature) + 1)*sizeof(char) + funcList->bytecodeSize;
		szs.bsize = funcList->bytecodeSize;
		szs.ssize = (strlen(funcList->signature) + 1)*sizeof(char);
		if(fwrite(&szs, sizeof(Sizes), 1, compiled) != 1)
		{
			FILE_ERROR_MESSAGE(compiled);
		}
		if(fwrite(funcList->signature, szs.ssize, 1, compiled) != 1)
		{
			FILE_ERROR_MESSAGE(compiled);
		}
		free(funcList->signature);
		idnamms.fid = funcList->id;
		idnamms.vAmm = funcList->varAmmount;
		idnamms.aAmm = funcList->argAmmount;
		if(fwrite(&idnamms, sizeof(IDAndAmmounts), 1, compiled) != 1)
		{
			FILE_ERROR_MESSAGE(compiled);
		}
		if(fwrite(funcList->bytecode, szs.bsize, 1, compiled) != 1)
		{
			FILE_ERROR_MESSAGE(compiled);
		}
		free(funcList->bytecode);
		tmp = (void*) funcList->next;
		free(funcList);
		funcList = (PFunctionsList) tmp;
	}
	fclose(compiled);
	fprintf(stderr, "assembly-/ Compilation successful. %llu lines compiled.\n", linecount);
	return 0;
}
